package modl

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"math"
)

func ReadByte(r *bufio.Reader, valName string) byte {
	temp, err := r.ReadByte()
	if err != nil {
		panic(NewReadWriteError(valName, err.Error()))
	}
	return temp
}

func WriteByte(val byte, w *bufio.Writer, valName string) {
	if err := w.WriteByte(val); err != nil {
		panic(NewReadWriteError(valName, err.Error()))
	}
}

func ReadBool(r *bufio.Reader, valName string) bool {
	return ReadByte(r, valName) != 0
}

func WriteBool(val bool, w *bufio.Writer, valName string) {
	if val {
		WriteByte(1, w, valName)
	} else {
		WriteByte(0, w, valName)
	}

}

func ReadUint8(r *bufio.Reader, valName string) uint8 {
	return uint8(ReadByte(r, valName))
}

func WriteUint8(val uint8, w *bufio.Writer, valName string) {
	WriteByte(byte(val), w, valName)
}

func ReadInt8(r *bufio.Reader, valName string) int8 {
	return int8(ReadByte(r, valName))
}

func WriteInt8(val int8, w *bufio.Writer, valName string) {
	WriteByte(byte(val), w, valName)
}

func ReadUint16(r *bufio.Reader, valName string) uint16 {
	var temp [2]byte
	buf := temp[:]
	if nbytes, err := r.Read(buf); err != nil || nbytes != 2 {
		panic(NewReadWriteError(valName, err.Error()+", nbytes: %d", nbytes))
	}
	return binary.BigEndian.Uint16(buf)
}

func WriteUint16(val uint16, w *bufio.Writer, valName string) {
	var temp [8]byte
	buf := temp[:]
	binary.BigEndian.PutUint16(buf, val)
	if _, err := w.Write(buf); err != nil {
		panic(NewReadWriteError(valName, err.Error()))
	}
}

func ReadInt16(r *bufio.Reader, valName string) int16 {
	return int16(ReadUint16(r, valName))
}

func WriteInt16(val int16, w *bufio.Writer, valName string) {
	WriteUint16(uint16(val), w, valName)
}

func ReadUint32(r *bufio.Reader, valName string) uint32 {
	var temp [4]byte
	buf := temp[:]
	if nbytes, err := r.Read(buf); err != nil || nbytes != 4 {
		panic(NewReadWriteError(valName, err.Error()+", nbytes: %d", nbytes))
	}
	return binary.BigEndian.Uint32(buf)
}

func WriteUint32(val uint32, w *bufio.Writer, valName string) {
	var temp [8]byte
	buf := temp[:]
	binary.BigEndian.PutUint32(buf, val)
	if _, err := w.Write(buf); err != nil {
		panic(NewReadWriteError(valName, err.Error()))
	}
}

func ReadInt32(r *bufio.Reader, valName string) int32 {
	return int32(ReadUint32(r, valName))
}

func WriteInt32(val int32, w *bufio.Writer, valName string) {
	WriteUint32(uint32(val), w, valName)
}

func ReadUint64(r *bufio.Reader, valName string) uint64 {
	var temp [8]byte
	buf := temp[:]
	if nbytes, err := r.Read(buf); err != nil || nbytes != 8 {
		panic(NewReadWriteError(valName, err.Error()+", nbytes: %d", nbytes))
	}
	return binary.BigEndian.Uint64(buf)
}

func WriteUint64(val uint64, w *bufio.Writer, valName string) {
	var temp [8]byte
	buf := temp[:]
	binary.BigEndian.PutUint64(buf, val)
	if _, err := w.Write(buf); err != nil {
		panic(NewReadWriteError(valName, err.Error()))
	}
}

func ReadInt64(r *bufio.Reader, valName string) int64 {
	return int64(ReadUint64(r, valName))
}

func WriteInt64(val int64, w *bufio.Writer, valName string) {
	WriteUint64(uint64(val), w, valName)
}

func ReadFloat32(r *bufio.Reader, valName string) float32 {
	return math.Float32frombits(ReadUint32(r, valName))
}

func WriteFloat32(val float32, w *bufio.Writer, valName string) {
	WriteUint32(math.Float32bits(val), w, valName)
}

func ReadFloat64(r *bufio.Reader, valName string) float64 {
	return math.Float64frombits(ReadUint64(r, valName))
}

func WriteFloat64(val float64, w *bufio.Writer, valName string) {
	WriteUint64(math.Float64bits(val), w, valName)
}

func ReadString(r *bufio.Reader, valName string) string {
	strlen := ReadUint32(r, valName+" length")
	buf := make([]byte, strlen)
	if nbytes, err := r.Read(buf); err != nil || uint32(nbytes) != strlen {
		panic(NewReadWriteError(valName, err.Error()))
	}
	return string(buf)
}

func WriteString(val string, w *bufio.Writer, valName string) {
	WriteUint32(uint32(len(val)), w, valName+" length")
	if _, err := w.Write([]byte(val)); err != nil {
		panic(NewReadWriteError(valName, err.Error()))
	}
}

type ReadWriteError struct {
	valName string
	err     string
}

func NewReadWriteError(valName string, errorfmt string, errParams ...interface{}) ReadWriteError {
	return ReadWriteError{valName, fmt.Sprintf(errorfmt, errParams...)}
}
